#ifndef __TERRAIN_BRUSH_H__
#define __TERRAIN_BRUSH_H__

#include <cstdint>
#include <functional>
#include <map>
#include "Engine/Point3.hpp"

using namespace PocketSurvival::Engine;

namespace PocketSurvival
{
    class TerrainBrush;
    class TerrainChunk;
    class SubsystemTerrain;

    struct TBCell
    {
        int8_t posX;
        int8_t posY;
        int8_t posZ;
        int32_t value;

        inline bool operator<(const Point3& other) const;
    };

    class TBBrush
    {
    public:
        int32_t value;
        std::function<int32_t(int32_t)> handler1 = nullptr;
        std::function<int32_t(const Point3&)> handler2 = nullptr;

        TBBrush(int32_t theValue);
        TBBrush(std::function<int32_t(int32_t)> handler);
        TBBrush(std::function<int32_t(const Point3&)> handler);

        inline int32_t paint(TerrainBrush *terrainBrush, const Point3 &point);
    };

    class TBCounter
    {
    public:
        int32_t value;
        std::function<int32_t(int32_t)> handler1 = nullptr;
        std::function<int32_t(const Point3&)> handler2 = nullptr;

        TBCounter(int32_t theValue);
        TBCounter(std::function<int32_t(int32_t)> handler);
        TBCounter(std::function<int32_t(const Point3&)> handler);

        inline int32_t count(TerrainBrush *terrainBrush, const Point3 &point);
    };
    


    class TerrainBrush
    {
    public:
        bool isCompile = false;
        std::map<int32_t, TBCell> cellsMap;

        static inline int32_t key(int32_t x, int32_t y, int32_t z);

        void compile();
        int32_t countNonDiagonalNeighbors(int32_t x, int32_t y, int32_t z, TBCounter *counter);
        void replace(int32_t oldValue, int32_t newValue);
        void calculateBounds(Point3 &min, Point3 &max);
        int32_t getValue(int32_t x, int32_t y, int32_t z);
        inline int32_t getValue(const Point3& point);
        void addCell(int32_t x, int32_t y, int32_t z, TBBrush *brush);

        void addBox(int32_t x, int32_t y, int32_t z, int32_t sizeX, int32_t sizeY, int32_t sizeZ, TBBrush *brush);
        void addRay(int32_t x1, int32_t y1, int32_t z1, int32_t x2, int32_t y2, int32_t z2, int32_t sizeX, int32_t sizeY, int32_t sizeZ, TBBrush *brush);
        void paintFastSelective(TerrainChunk *chunk, int32_t x, int32_t y, int32_t z, int32_t onlyInValue);
        void paintFastAvoidWater(SubsystemTerrain *terrain, TerrainChunk *chunk, int32_t x, int32_t y, int32_t z);
        void paintFast(TerrainChunk *chunk, int32_t x, int32_t y, int32_t z);
        void paint(SubsystemTerrain *terrain, int32_t x, int32_t y, int32_t z);
    };

    inline int32_t TerrainBrush::key(int32_t x, int32_t y, int32_t z)
    {
        return y + 128 + (x + 128 << 8) + (z + 128 << 16);
    }
    inline int32_t TerrainBrush::getValue(const Point3& point)
    {
        return getValue(point.posX, point.posY, point.posZ);
    }



    inline TBBrush::TBBrush(int32_t theValue) : value(theValue){}
    inline TBBrush::TBBrush(std::function<int32_t(int32_t)> handler) : handler1(handler) {}
    inline TBBrush::TBBrush(std::function<int32_t(const Point3&)> handler) : handler2(handler) {}
    
    inline TBCounter::TBCounter(int32_t theValue) : value(theValue){}
    inline TBCounter::TBCounter(std::function<int32_t(int32_t)> handler) : handler1(handler) {}
    inline TBCounter::TBCounter(std::function<int32_t(const Point3&)> handler) : handler2(handler) {}


    inline bool TBCell::operator<(const Point3& other) const
    {
        return TerrainBrush::key(posX,posY,posZ) < TerrainBrush::key(other.posX,other.posY,other.posZ);
    }
    inline int32_t TBBrush::paint(TerrainBrush *terrainBrush, const Point3 &point)
    {
		if (handler1 != nullptr)
		{
			return handler1(terrainBrush->getValue(point.posX, point.posY, point.posZ));
		}
		if (handler2 != nullptr)
		{
			return handler2(point);
		}
		return value;
    }

    inline int32_t TBCounter::count(TerrainBrush *terrainBrush, const Point3 &point)
    {
		if (handler1 != nullptr)
		{
			return handler1(terrainBrush->getValue(point.posX, point.posY, point.posZ));
		}
		if (handler2 != nullptr)
		{
			return handler2(point);
		}
        if(terrainBrush->getValue(point) != value)
        {
            return 0;
        }
		return value;
    }
} // namespace PocketSurvival

#endif
